#include <stdio.h>// snprintf，printf
#include <string.h>   // 提供 NULL strlen() strcpy()
#include <stdbool.h> // _Bool
#include <stdlib.h>// malloc，char *str = (char*)malloc(total_size); free
#include <unistd.h>// sleep(1); //秒，usleep(500000); //微秒
#include <SDL2/SDL.h>// SDL_Delay(1000); // 毫秒

#include "lvgl/lvgl.h"
#include "config/CustomConfig.h"
#include <curl/curl.h>
#include "utils/network/api_request_mgr.h"
#include "utils/str_util.h"
#include "utils/network/json_analysis.h"
#include "assets/fonts/single_color_emoji.h"

#include "src/home/home.h"
#include "data_handle.h"

#include "include/common.h"
#include "utils/components/navigation_bar.h"
#include "utils/components/sdl_disp_window.h"
#include <cjson/cJSON.h>
#include "utils/network/json_analysis.h"
#include "src/av_foundation/audio_play_ui.h"
#include "assets/fonts/chinese/lv_font_sans_sc_14.h"


lv_obj_t * data_handle_screen; // 在.h通过extern声明data_handle_screen但不分配内存，然后在.c文件即此处给data_handle_screen分配内存
static lv_timer_t *slider_timer;//lvgl 定时器，用来定时每100毫秒调用一次回调函数
static Uint32 count_millisecs;//lvgl 定时器的每100毫秒累计和
static Uint32 time_out = 10;//超时时间设置10s即10000ms

#define ITEM_COUNT 9

// static全局变量只在当前.c文件可见，不会与其他.c文件发生重复的情况，否则重复情况会报错❌：duplicate symbol '_items' in:
// 即使全局变量只在.c文件声明而不是.h文件声明，也不能和其他.c文件的全局变量重复，否则会报错❌：duplicate symbol '_items' in:
// 💡 C 语言里，全局变量在没有 static 修饰时，是具有外部链接性（external linkage），会导出到整个目标文件的符号表中。
// 所以两个 .c 文件定义了相同名字的变量，就会“重复定义”。
static HomeItem items[ITEM_COUNT] = {
    {"🏠", "network"},
    {"📁", "play mp3"},
    {"📁", "string"},
    {"❓", "int"},
    {"⚙️", "bool"},
    {"⚙️", "struct"},
    {"⚙️", "sleep & delay"},
    {"⚙️", "free释放内存"},
    {"⚙️", "handle json"},
};
static void handle_http_network() {
    printf("handle_http_network\n");
    cJSON *resultJson = queryAlbumTags();
    int code = getCJSONInt(resultJson, "code");
    if (code == REQ_SUCC_CODE)
    {
        printf("code value: %d\n", code);
        const cJSON *data = cJSON_GetObjectItemCaseSensitive(resultJson, "data");
        int data_type = data->type;  // data的实际类型
        printf("data_type = %d, cJSON_Array = %d\n", data_type, cJSON_Array);// cJSON data的实际类型
        //data_type = 32, cJSON_Array = 32
        if (!cJSON_IsArray(data)) {
            printf("data isn't array type\n");
            return;
        }
        // 获取 data 数组的第一个元素
        const cJSON *first_item = cJSON_GetArrayItem(data, 0);
        if (!cJSON_IsObject(first_item)) {
            printf("data[0] 不是对象\n");
            return;
        }

        // 获取 firstName 字段
        char *firstName = getCJSONString(first_item, "firstName");
        printf("firstName: %s\n", firstName);
        set_window_title(home_disp, firstName);
    } else
    {
        char *result = getCJSONString(resultJson, "result");
        printf("queryAlbumTags failed: %s\n", result);
    }
    cJSON *homeDataResultJson = queryHomeData();
    char *homeDataResultJsonStr = json_to_string(homeDataResultJson);
    printf("queryHomeData result: %s\n", homeDataResultJsonStr);
}
static void handle_json() {
    char *json_text =
        "{"
        "\"errno\": 0,"
        "\"total\": 2205465706496,"
        "\"free\": 2205465706496,"
        "\"request_id\": 4890482559098510375,"
        "\"expire\": false,"
        "\"used\": 686653888910"
        "}";
    cJSON *json = string_parse_to_jsno(json_text);
    int errno = getCJSONInt(json, "errno");
    printf("json_text errno: %d\n", errno);
}
static void timer_event() {
    printf("timer event\n");
    count_millisecs += 100;//每100毫秒触发该回调函数，因此此处累加100ms
    if (count_millisecs >= time_out * 1000) {
        printf("timer event time out\n");
        //已超时，结束定时器即删除定时器
        lv_timer_del(slider_timer);  // slider_timer 是 lv_timer_t*，之前创建的定时器
        slider_timer = NULL;          // 避免悬空指针
    }
}

/**
 * sleep usleep SDL_delay休眠函数处理
 * @param void
 */
static void sleep_and_delay() {
    //sleep()（秒） 或 usleep()（微秒）
    printf("sleep 1 sec\n");
    sleep(1);      // 休眠 1 秒
    printf("usleep 0.1 sec\n");
    usleep(500000); // 休眠 0.5 秒
    printf("SDL_Delay 1 sec\n");
    SDL_Delay(1000); // 毫秒
    printf("sleep_and_delay end\n");

    // ✅在 LVGL 中：所有 UI 操作（包括更新进度条）都必须在 LVGL 主线程或 lv_timer_handler() 调用的上下文内执行
    // ❌否则UI更新无效，因此此处在播放时创建LVGL定时器lv_timer来更新 UI
    // ✅启动 LVGL 定时器lv_timer，每100ms调用一次timer_event函数，一直循环调用timer_event回调函数直到删除定时器如下：
    // lv_timer_del(slider_timer);  // slider_timer 是 lv_timer_t*，之前创建的定时器
    // slider_timer = NULL;          // 避免悬空指针

    slider_timer = lv_timer_create(timer_event, 100, NULL);
}

/**
 * free内存释放函数分析；
 *  内存来源	                        是否需要 free()	    说明
    malloc() / calloc() / realloc()	   ✅ 需要	         动态分配在堆上，必须手动释放
    局部变量（栈上）	                  ❌ 不需要	        函数结束后自动销毁
    全局 / 静态变量	                     ❌ 不需要	        程序退出时系统回收
    字面量字符串 "abc"	                 ❌ 不需要	        编译期常量，静态存储区
 * @param void
 */
static void handle_free() {
    // 1️⃣ malloc() / calloc() / realloc()，动态分配在堆上，必须手动free()释放
    char *msg = (char *)malloc(100);// 分配的内存在堆上，函数结束不会自动释放，必须free()
    strcpy(msg, "Hello, malloc!");
    printf("msg: %s\n", msg);
    // ✅ 必须手动释放
    free(msg);

    // 2️⃣ 局部变量（栈上），函数结束后自动销毁，不可free()，也不可返回它的指针
    // ❌ 注意：这是局部变量，会在函数结束后销毁
    // 返回指针会导致悬空指针（危险）
    char msg2[] = "Hello, stack!";// 分配的内存在栈上，❌栈上变量在函数返回时自动释放，不可free()，也不可返回它的指针。
    printf("msg2: %s\n", msg2);

    // 3️⃣ 返回全局 / 静态变量或字符串字面量（不需要 free），编译期常量，静态存储区
    static char msg3[] = "Static hello";
    printf("msg3: %s\n", msg3); // "Static hello" // OK，不需要 free

    // 4️⃣ 复合场景（malloc + strdup），结构体中含有 malloc 字段，✅ 要分别 free() 内部成员后再 free() 自身
//     typedef struct {
//         char *name;
//     } User;

//     User* create_user(const char *name) {
//         User *u = malloc(sizeof(User));
//         u->name = strdup(name); // strdup 内部会 malloc
//         return u;
//     }

//     void destroy_user(User *u) {
//         free(u->name); // 必须释放 name
//         free(u);       // 必须释放结构体本身
//     }
    // User *user = create_user("Tom");
    // printf("%s\n", user->name);
    // destroy_user(user); // ✅ 正确释放

}

/**
 * char[]字符数组字符串数据处理
 * @param void
 */
static void handle_string() {
    char *str1 = "str1,";
    char *str2 = "str2";
    char *new_str = plus_str(str1, str2);
    printf("new_str: %s\n", new_str);
    printf(is_empty_str(str1) ? "str1 is empty\n" : "str1 is not empty\n");
    char *str3 = "";
    printf(is_empty_str(str3) ? "str3 is empty\n" : "str3 is not empty\n");
    printf(is_windows_sys() ? "win sys\n" : "macos linux etc\n");

    char *json_text =
    "{"
    "    \"device_code\": {\"code\": \"984c2459ec41415c9f5b0137a017fc49\"},"
    "    \"user_code\": \"8drp69hk\","
    "    \"verification_url\": \"https://openapi.baidu.com/device\","
    "    \"qrcode_url\": \"https://openapi.baidu.com/device/qrcode/a52610ef0eec30da5e45870872317478/8drp69hk\","
    "    \"expires_in\": 300,"
    "    \"interval\": 5"
    "}";
    cJSON *json = string_parse_to_jsno(json_text);
    cJSON *device_code_json = cJSON_GetObjectItemCaseSensitive(json, "device_code");
    char *code = getCJSONString(device_code_json, "code");
    char *user_code = getCJSONString(json, "user_code");
    printf("parsed json: %s, device_code: %s, code: %s\n", json_to_string(json), user_code, code);
    //parsed json: {"device_code":{"code":"984c2459ec41415c9f5b0137a017fc49"},"user_code":"8drp69hk","verification_url":"https://openapi.baidu.com/device","qrcode_url":"https://openapi.baidu.com/device/qrcode/a52610ef0eec30da5e45870872317478/8drp69hk","expires_in":300,"interval":5}, device_code: 8drp69hk, code: 984c2459ec41415c9f5b0137a017fc49
}

/**
 * int Uint32整型数据处理
 * @param void
 */
static void handle_int_long_double_float() {
    Uint32 cur_ticks = SDL_GetTicks(); // 获取SDL初始化后经过的毫秒数即系统的毫秒级计时器。
    int num = 1; // 4个字节，±2.1×10^9

    // long类型：在Windows：4字节等同于int32，在MacOS：8字节，约 ±9.2×10^18
    // ✅long long 在不同平台都是安全的8字节即64位整数，比long更统一
    // long num_plus = 11191;
    long long num_plus = 11191;//printf对于long是ld，long long是lld

    double price = 1.1; // 8字节，15位精度，±1.7×10³⁰⁸
    float percent = 0.91f; // 4字节，7位精度，±3.4×10³⁸，0.91默认是double，因此需写为0.91f才是float
    // ✅浮点数不要用 == 判断相等，因为它们在计算中存在精度误差，而要判断“是否接近”。
    #define EPSILON 1e-6   // 精度容差，可根据需求调整
    _Bool is_equal = fabs(price - 1.1) < EPSILON;

    //bool类型（来自<stdbool.h>）实际上是一个整数类型的别名，true对应1，false对应0。所以不能直接用%s打印它，而应该根据其值打印字符串。
    char *result = is_equal ? "equal" : "not equal";
    char letter = 'A';
// ✅char *用%s，char字符用%c，否则编译报错❌：
// warning: format specifies type 'char *' but the argument has type 'char' [-Wformat]
//   179 |              "%s:%s%sassets%s%s",
//       |               ~~
//       |               %c
//   180 |              LV_FS_STDIO_LETTER, cwd, PATH_SEP, PATH_SEP, relative_path);

// ✅%p打印指针类型，如果输出是 (nil) 或 0x0，那说明指针没创建成功
    printf("result pointer: %p, milliseconds after sdl init: %u, letter:%c, num: %d, num_plus: %lld, price: %f, percent: %f, result: %s, is_equal: %s\n",
            (void*)result, cur_ticks, letter, num, num_plus, price, percent, result, is_equal ? "equal" : "not equal");
    // milliseconds after sdl init: 26014, num: 1, long_num: 11191, price: 1.100000, percent: 0.910000, result: equal
}

/* 点击事件回调
加上 static → 限制作用域，防止外部文件访问
*/
static void item_event_cb(lv_event_t * e)
{
    int click_index = (int)(intptr_t)lv_event_get_user_data(e);  // ✅ 从 user_data 获取索引
    const char *text = items[click_index].text;
    printf("Clicked item at: %d %s\n", click_index, text);
    switch (click_index)
    {
    case 0: {//{ } 包裹起来后，变量作用域只在这个 case 内
        // 提示警告信息：❌warning: label followed by a declaration is a C23 extension [-Wc23-extensions]
        // case 0:或者some_label:等标签的后面直接跟着变量声明在C99/C11标准里不允许，涉及标签label与变量声明declaration的处理；
        // 推荐✅方案1️⃣ { }包裹起来后即可
        // 方案2️⃣：在标签前声明变量JSON *resultJson;然后标签后面再赋值：some_label:resultJson = queryAlbumTags();
        // cJSON *resultJson = queryAlbumTags();
        handle_http_network();
        break;
    }
    case 1: {
        /* go to audio play screen */
        audio_play_ui();
        break;
    }
    case 2: {
        handle_string();
        break;
    }
    case 3: {
        handle_int_long_double_float();
        break;
    }
    case 6: {
        sleep_and_delay();
        break;
    }
    case 7: {
        handle_free();
        break;
    }
    case 8: {
        handle_json();
        break;
    }
    default:
        break;
    }
    // 获取项目当前LVGL版本，LVGL version: 9.4.0
    // printf("LVGL version: %d.%d.%d\n", LVGL_VERSION_MAJOR, LVGL_VERSION_MINOR, LVGL_VERSION_PATCH);
}

static void back_btn_event_handler(lv_event_t * e)
{
    printf("back_btn_event_handler\n");
    lv_screen_load(home_screen);//返回主屏幕
    set_window_title(home_disp, "Home");
    // lv_scr_load()只是将活动屏幕切换为新的screen，把旧屏幕从显示链上移除（不显示），但不会删除旧屏幕对象本身
    // 因此如果不再需要data_handle_screen必须手动释放，否则它会一直占用内存：
    lv_obj_del(data_handle_screen);

}

// 外部变量声明（extern）不会分配任何内存，等价于：extern const lv_img_dsc_t back;
// lv_obj_t *img = lv_img_create(lv_scr_act());才会在运行时从堆上分配一个 LVGL 控件对象 的堆内存
// lv_img_set_src(img, &back);&back 指向的内存是编译时固定的常量地址
LV_IMG_DECLARE(back);

/* 创建 data_handle 界面 */
void data_handle_ui(void)
{
    set_window_title(home_disp, "Data handle");
    data_handle_screen = lv_obj_create(NULL);
    // 添加导航栏
    lv_obj_t *nav = normal_nav_bar_create(data_handle_screen, "data handle", back_btn_event_handler);

    /* 创建一个滚动页面容器 */
    // 通过lv_obj_create()方法在lv_scr_act()当前活动屏幕创建一个基础对象（容器），这个容器可以包含子对象（比如按钮、标签等）。
    // 返回值(list)是一个指向这个新建对象的指针。
    // lv_obj_t * list = lv_obj_create(lv_scr_act());
    lv_obj_t * list = lv_obj_create(data_handle_screen);//在data_handle_screen主屏幕创建
    // 设置 flex 布局，垂直排列
    lv_obj_set_flex_flow(list, LV_FLEX_FLOW_COLUMN);
    // 设置容器的滚动方向，LV_DIR_VER 表示“垂直方向可滚动”，如果内容超出容器高度，就可以上下滚动。
    lv_obj_set_scroll_dir(list, LV_DIR_VER);

    // 设置这个容器的宽和高
    lv_obj_set_size(list, DISP_WIDTH, DISP_HEIGHT - 40);
    //Q: ✅ lv_obj_align_to(obj1, obj2, LV_ALIGN_OUT_BOTTOM_MID, offsetx, offsety)
    // LV_ALIGN_OUT_BOTTOM_MID表示外部对齐，也就是obj1在obj2的外部然后进行对齐即obj1顶部中心点与obj2底部中心点进行对齐
    // 横向偏移offsetx px即obj1顶部中心点与obj2底部中心点横向偏移offsetx个像素点
    // 纵向偏移offsety即obj1顶部中心点与obj2底部中心点纵向偏移16px
    lv_obj_align_to(list, nav, LV_ALIGN_OUT_BOTTOM_MID, 0, 16);

    // 设置容器上下左右的内边距8像素，最后一个参数 0 是样式选择器，0 表示默认状态；
    lv_obj_set_style_pad_all(list, 8, 0);
    // 设置容器的背景颜色，最后一个参数0 是样式选择器，0表示默认状态。
    lv_obj_set_style_bg_color(list, lv_color_hex(0xfafafa), 0);

    lv_obj_set_style_pad_row(list, 8, 0);   // 子对象垂直间距
    for (int i = 0; i < ITEM_COUNT; i++) {
        /* 在容器list内创建一个 item 按钮对象 */
        lv_obj_t *item = lv_btn_create(list);
        //设置对象item的宽度为父容器的 100%；高度固定为 60 像素。
        lv_obj_set_size(item, lv_pct(100), 60);
        //背景色
        lv_obj_set_style_bg_color(item, lv_color_hex(0xffffff), 0);
        //圆角 8px
        lv_obj_set_style_radius(item, 8, 0);
        //内边距 10px
        lv_obj_set_style_pad_all(item, 10, 0);
        //阴影宽度 5px
        lv_obj_set_style_shadow_width(item, 5, 0);
        //透明度 20%
        lv_obj_set_style_shadow_opa(item, LV_OPA_20, 0);

        /* icon */
        //在按钮上创建一个图标标签
        lv_obj_t *icon = lv_label_create(item);
        //设置标题
        lv_label_set_text(icon, items[i].icon);
        lv_obj_set_style_text_font(icon, &single_color_emoji, LV_PART_MAIN);
        lv_obj_set_style_text_color(icon, lv_color_hex(0x128371), 0);
        //设置左边与父视图左中对齐
        lv_obj_align(icon, LV_ALIGN_LEFT_MID, 0, 0);

        /* label */
        //在按钮上创建一个图标标签
        lv_obj_t *label = lv_label_create(item);
        //设置标题
        lv_label_set_text(label, items[i].text);
        lv_obj_set_style_text_font(label, &lv_font_sans_sc_14, 0);
        //设置该对象整体与icon对象的右边距离10px
        lv_obj_align_to(label, icon, LV_ALIGN_OUT_RIGHT_MID, 10, 0);
        //普通字符使用 lv_font_montserrat_20，字号 20。
        //中文字符则需要自己导入，此处使用自行导入的中文字符集chinese_font（需包含英文数字等常用字符）
        // lv_obj_set_style_text_font(label, &chinese_font, 0);
        //lv_obj_set_style_text_color设置字体颜色，lv_color_hex(0xfafafa)初始化颜色结构体lv_color_t
        lv_obj_set_style_text_color(label, lv_color_hex(0x128371), 0);

        /* 点击回调
        //Q: ✅C语言编译顺序问题：
        报错：❌error: use of undeclared identifier 'item_event_cb'
        108 | lv_obj_add_event_cb(item, item_event_cb, LV_EVENT_CLICKED, label);
        ✅原因分析：函数必须在使用前声明或定义即提前声明函数，否则编译器不知道这个函数的类型和参数，因此item_event_cb事件方法必须写在上面
        */
        //Q: int传参指针类型：最后一个参数是一个void *user_data指针，int值类型需采用(void *)转换成是一个指针类型，
        // 用intptr_t/uintptr_t可确保在32/64位平台上安全转换
        lv_obj_add_event_cb(item, item_event_cb, LV_EVENT_CLICKED, (void *)(intptr_t)i);

        /* 添加间距 */
        //给 list 容器的底部留 8 像素的间距；
        lv_obj_set_style_pad_bottom(list, 8, 0);
    }

    //Q: ✅调用lv_screen_load方法把这个data_handle_screen设置为当前活动屏幕替换掉原来的home_screen主屏幕而不是遮挡而已
    // 此时调用lv_scr_act()方法会返回该data_handle_screen
    // home_screen是一个指针，指向当前活动屏幕对象，被替换掉当前活动屏幕后不会变成NULL即不会变，
    // 也不会自动释放（除非手动调用lv_obj_del(home_screen)），它仍然指向原来的屏幕对象，只是这个屏幕不再是活动屏幕
    lv_screen_load(data_handle_screen);
}

//❌异常问题：
// 1、 ❌make -j编译构建可执行文件时报错：
// Linking CXX executable F:\c\EmbeddedDevelopment\demos\lv_port_pc_vscode\bin\main.exe
// F:/c/EmbeddedDevelopment/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/15.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot open output file F:\c\EmbeddedDevelopment\demos\lv_port_pc_vscode\bin\main.exe: Permission denied
// collect2.exe: error: ld returned 1 exit status
// make[2]: *** [CMakeFiles\main.dir\build.make:388: F:/c/EmbeddedDevelopment/demos/lv_port_pc_vscode/bin/main.exe] Error 1
// make[1]: *** [CMakeFiles\Makefile2:150: CMakeFiles/main.dir/all] Error 2
// make: *** [Makefile:135: all] Error 2
// ✅原因分析：/lv_port_pc_vscode/bin/main.exe正在使用中
// ✅解决办法：先暂停该可执行文件的运行，才能重新构建新的main.exe


// 调整项目目录结构：components移动到utils底下，简化项目一级目录；

// 2、 ❌make -j编译构建可执行文件时报错：
// F:/c/EmbeddedDevelopment/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/15.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe:
// CMakeFiles\main.dir/objects.a(audio_play_ui.c.obj): in function `update_slider':
// F:/c/EmbeddedDevelopment/demos/lv_port_pc_vscode/src/av_foundation/audio_play_ui.c:75:(.text+0x12a): undefined reference to `config_slider_progress'
// collect2.exe: error: ld returned 1 exit status
// make[2]: *** [CMakeFiles\main.dir\build.make:439: F:/c/EmbeddedDevelopment/demos/lv_port_pc_vscode/bin/main.exe] Error 1
// make[1]: *** [CMakeFiles\Makefile2:150: CMakeFiles/main.dir/all] Error 2
// make: *** [Makefile:135: all] Error 2
// ✅原因分析：虽然有inclue方法对应的.h文件，.c和.h的方法名、参数类型和参数名有保持一致，CMakeList.txt的add_executable有包括该.c文件
// 但是仍然报错，经过分析是因为新增该.c文件后虽有通过CMakeList.txt的add_executable加到编译列表，但是没有执行编译指令：```cmake .. -G "MinGW Makefiles"```生成新的自动化编译脚本Makefiles
// ✅解决办法：在根目录创建build文件夹，如果build文件夹已创建则先清空该文件夹，再打开"MSYS2 MINGW64"软件，
//  进入build文件夹路径：```cd /f/c/EmbeddedDevelopment/demos/lv_port_pc_vscode/build```，
//  执行编译指令：```cmake .. -G "MinGW Makefiles"```；

// 3、 ❌图像未显示：
// ✅原因分析：可能是组件对象的宽或高为0导致处于隐藏状态
// ✅解决办法：打印出组件对象的宽高检查是否为0
